package pers.mihao.quickstream.common.util;

import org.springframework.util.ReflectionUtils;
import pers.mihao.quickstream.common.exception.BaseException;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * @author mihao
 * @version 1.0
 * @since 2020\9\20 0020 11:14
 */
public final class ReflectionUtil {

    /**
     * 单例缓存
     */
    private static final Map<Class<?>, Object> SINGLETON_MAP = new HashMap<>(16);

    private ReflectionUtil() {
    }

    public static boolean classHasSelfMethod(Class<?> clazz, String method) {
        return true;
    }

    public static boolean hasAnnFiled(Class<?> clazz, Class<? extends Annotation> annotation) {
        return !ReflectionUtil.getAllFiled(clazz,
                field -> field.getAnnotation(annotation) != null).isEmpty();
    }

    public static List<Field> getAnnFiledList(Class<?> clazz, Class<? extends Annotation> annotation) {
        return ReflectionUtil.getAllFiled(clazz,
                field -> field.getAnnotation(annotation) != null);
    }

    /**
     * 通过属性名获取Field对象
     *
     * @param fieldName
     * @param cls
     * @return
     */
    public static Field getFieldByName(String fieldName, Class<?> cls) {
        Field field = null;
        for (; cls != Object.class; cls = cls.getSuperclass()) {
            try {
                field = cls.getDeclaredField(fieldName);
            } catch (NoSuchFieldException e) {
                //
            }
            if (field != null) {
                return field;
            }
        }
        return field;
    }

    /**
     * 通过对象.class获取所有Fields，包括父类
     *
     * @param clazz
     * @return
     */
    public static List<Field> listFields(Class<?> clazz) {
        List<Field> fields = new ArrayList<>();
        if (clazz.getSuperclass() != null) {
            fields.addAll(listFields(clazz.getSuperclass()));
        }
        Field[] fs = clazz.getDeclaredFields();
        fields.addAll(Arrays.asList(fs));
        return fields;
    }

    public static boolean fieldExist(String fieldName, Class<?> cls) {
        return getFieldByName(fieldName, cls) != null;
    }


    public static List<Method> getAllMethod(Class<?> clazz, Function<Method, Boolean>... filters) {
        List<Method> methods = new ArrayList<>();
        for (Method method : ReflectionUtils.getAllDeclaredMethods(clazz)) {
            boolean flag = true;
            for (Function<Method, Boolean> filter : filters) {
                if (!Boolean.TRUE.equals(filter.apply(method))) {
                    flag = false;
                    break;
                }
            }
            if (flag) {
                methods.add(method);
            }
        }
        return methods;
    }

    @SafeVarargs
    public static List<Field> getAllFiled(Class<?> clazz, Function<Field, Boolean>... filters) {
        List<Field> fields = new ArrayList<>();
        while (clazz != null && clazz != Object.class) {
            for (Field field : clazz.getDeclaredFields()) {
                boolean flag = true;
                for (Function<Field, Boolean> filter : filters) {
                    if (!Boolean.TRUE.equals(filter.apply(field))) {
                        flag = false;
                        break;
                    }
                }
                if (flag) {
                    fields.add(field);
                }
            }
            clazz = clazz.getSuperclass();
        }
        return fields;
    }



    public static Method getMethod(Class<?> clazz, String methodName, Class<?>... classes) {
        List<Method> methods = Arrays.stream(ReflectionUtils.getAllDeclaredMethods(clazz))
                .filter(method -> methodName.equals(method.getName()))
                .filter(method -> method.getParameterCount() == (classes == null ? 0 : classes.length))
                .collect(Collectors.toList());
        Method target = null;
        for (Method method : methods){
            if (isSameMethodType(method.getParameterTypes(), classes)) {
                target = method;
                target.setAccessible(true);
                break;
            }
        }
        if (target != null) {
            return target;
        }

        for (Method method : methods){
            if (isSameMethodOrExtendType(method.getParameterTypes(), classes)) {
                target = method;
                target.setAccessible(true);
                break;
            }
        }

        return target;
    }

    private static boolean isSameMethodOrExtendType(Class<?>[] parameterTypes, Class<?>[] classes) {
        for (int i = 0; i < parameterTypes.length; i++) {
            if (parameterTypes[i] != classes[i] && !parameterTypes[i].isAssignableFrom(classes[i])){
                return false;
            }
        }
        return true;
    }

    /**
     * 相同的参数类型
     * @param parameterTypes
     * @param classes
     * @return
     */
    private static boolean isSameMethodType(Class<?>[] parameterTypes, Class<?>[] classes) {
        for (int i = 0; i < parameterTypes.length; i++) {
            if (parameterTypes[i] != classes[i]){
                return false;
            }
        }
        return true;
    }

    /**
     * 获取类上的注解
     * @param clazz
     * @param a
     * @return
     */
    public Annotation getAnnotation(Class<?> clazz, Class<? extends Annotation> a) {
        Annotation[] annotations = clazz.getAnnotations();
        for (int i = 0; i < annotations.length; i++) {
            if (annotations[i] != null && annotations[i].getClass().equals(a)) {
                return annotations[i];
            }
        }
        return null;
    }

    /**
     * 通过反射获取对象
     *
     * @param obj
     * @param delegate
     * @return
     */
    public static Object getValueByStaticFieldName(Class<?> obj, String delegate) {
        Field filed = getFieldByName(delegate, obj);
        return getValueByField(null, filed);
    }

    /**
     * 通过反射获取对象
     *
     * @param obj
     * @param delegate
     * @return
     */
    public static Object getValueByFieldName(Object obj, String delegate) {
        Field filed = getFieldByName(delegate, obj.getClass());
        return getValueByField(obj, filed);
    }

    /**
     * 通过反射获取对象
     *
     * @param obj
     * @param filed
     * @return
     */
    public static Object getValueByField(Object obj, Field filed) {
        Object value = null;
        if (filed != null) {
            try {
                if (filed.isAccessible()) {
                    value = filed.get(obj);
                } else {
                    filed.setAccessible(true);
                    value = filed.get(obj);
                }
            } catch (IllegalAccessException e) {
                throw new BaseException(e);
            }
        }
        return value;
    }

    /**
     * 反射设置值
     *
     * @param object
     * @param fieldName
     * @param fieldValue
     */
    public static void setValueByFieldName(Object object, String fieldName, Object fieldValue) {
        Field field = getFieldByName(fieldName, object.getClass());
        if (field != null) {
            try {
                if (field.isAccessible()) {
                    field.set(object, fieldValue);
                } else {
                    field.setAccessible(true);
                    field.set(object, fieldValue);
                    field.setAccessible(false);
                }
            } catch (IllegalAccessException e) {
                // doNoting
            }
        }
    }

    /**
     * 获取一个新的实例
     *
     * @param invoke
     * @param <T>
     * @return
     */
    public static <T> T getNewInstance(Class<T> invoke) throws IllegalAccessException, InstantiationException {
        return invoke.newInstance();
    }


    /**
     * 获取一个单例对象
     *
     * @param invoke
     * @param <T>
     * @return
     * @throws InstantiationException
     * @throws IllegalAccessException
     */
    public static <T> T getSingleton(Class<T> invoke) throws InstantiationException, IllegalAccessException {
        Object obj = SINGLETON_MAP.get(invoke);
        if (obj == null) {
            obj = getNewInstance(invoke);
            SINGLETON_MAP.put(invoke, obj);
        }
        return (T) obj;
    }
}
